Vue深入组件 - 程序化的事件监听器
[toc]
1. 快速认识
一句话,来说,在同一个Vue实例中,$on
、$once
用于设定监听器, 不同的是,$once
仅会被触发一次后立即失效。 而$off
则是一个主动的监听器销毁器。 $emit
则用于触发通过$on
,$once
设定的监听器,即事件触发器/发射器。
以下是一个简单的例子:
<template> <div> <button @click="$emit('cusEvent', 'cusEvent trigger~')">Emit cusEvent</button> <button @click="$off('cusEvent')">destory cusEvent</button> <br> <button @click="$emit('cusOnceEvent','only once chance to trigger~~~')">Emit OnceEvent</button> </div> </template> <script> export default { created() { this.$on("cusEvent", (msg) => { console.log(msg,'--line13'); }); this.$once("cusOnceEvent",(msg)=>{ console.log(msg,'--line20'); }) }, }; </script>
copy success
在生命周期created
钩子中,定义了两个事件监听器,分别是$on
监听的cusEvent
和 $once
监听的cusOnceEvent
.从演示中看到,执行操作的结果符合我们的预期,$once
和$on
都可以通过$emit
触发,此外,$once
仅触发一次,且$on
在被$off
销毁后,不再触发。
2. 进一步认识, 使用方法
2.1 vm.$on( event, callback )
参数:
{string | Array<string>} event
(数组只在 2.2.0+ 中支持){Function} callback
用法:
监听当前实例上的自定义事件。事件可以由
vm.$emit
触发。回调函数会接收所有传入事件触发函数的额外参数。
传入的监听函数,并不是只能是单个单个注册,可以是一个数组,以$on
为例:
<template> <div> <button @click="$emit('cusE01','E01')">cusE01</button> <button @click="$emit('cusE02','E02','hello')">cusE02</button> <button @click="$emit('cusE03','E03','hello','world')">cusE03</button> </div> </template> <script> export default { created(){ this.$on(['cusE01','cusE02','cusE03'],(...params)=>{ console.log(params,'--line12'); // ['E01'] '--line12' // ['E02', 'hello'] '--line12' // ['E03', 'hello', 'world'] '--line12' }) } }; </script> <style></style>
copy success
2.2 vm.$once( event, callback )
参数:
{string} event
{Function} callback
用法:
监听一个自定义事件,但是只触发一次。一旦触发之后,监听器就会被移除。
注意,$once
接收参数仅可为一个,不可为数组,若为数组,在触发任意一个监听器后,全部即刻失效。
如下示例:
<template> <div> <button @click="$emit('cusOnceE01','OnceE01')">cusOnceE01</button> <button @click="$emit('cusOnceE02','OnceE02','hello')">cusOnceE02</button> <button @click="$emit('cusOnceE03','OnceE03','hello','world')">cusOnceE03</button> </div> </template> <script> export default { created(){ this.$once(['cusOnceE01','cusOnceE02','cusOnceE03'],(...params)=>{ console.log(params,'--line12'); // 以下输出仅在相应按钮点击时触发一次,立刻失效,即以下输出只能单次输出其中一个 // ['OnceE01'] '--line12' // 或 // ['OnceE02', 'hello'] '--line12' // 或 // ['OnceE03', 'hello', 'world'] '--line12' }) } }; </script> <style></style>
copy success
2.3 vm.$off( [event, callback] )
参数:
{string | Array<string>} event
(只在 2.2.2+ 支持数组){Function} [callback]
用法:
移除自定义事件监听器。
- 如果没有提供参数,则移除所有的事件监听器;
- 如果只提供了事件,则移除该事件所有的监听器;
- 如果同时提供了事件与回调,则只移除这个回调的监听器。
2.3.1 同时销毁多个监听器
指定销毁:当$off
传入一个包含监听器事件名的数组作为第一个参数时,将可以指定的销毁当前实例多个定时器。
全部销毁:当$off
不传入任何参数时,将移除当前实例的所有监听器。
<template> <div> <button @click="$emit('cusE01','E01')">cusE01</button> <button @click="$emit('cusE02','E02','hello')">cusE02</button> <button @click="$emit('cusE03','E03','hello','world')">cusE03</button> <button @click="$off(['cusE01','cusE02'])">Off Multi</button><!--指定销毁/移除多个--> <button @click="$off()">Off All</button><!--全部销毁/移除--> </div> </template> <script> export default { created(){ this.$on(['cusE01','cusE02','cusE03'],(...params)=>{ console.log(params,'--line12'); // ['E01'] '--line12' // ['E02', 'hello'] '--line12' // ['E03', 'hello', 'world'] '--line12' }) } }; </script>
copy success
2.4 vm.$emit( eventName, […args] )
参数:
{string} eventName
[...args]
触发当前实例上的事件。附加参数都会传给监听器回调。
注意,同时只能触发一个监听器事件。
3. 两个组件传值可通过事件总线EventBus实现究竟是怎么回事?
分析:
首先,我们知道,$on
,$once
,$emit
等等这些是"实例事件/方法",也就是说,他们是Vue实例上的自带方法。 我们在<script></script>
标签包裹的部分去使用这些方法时,前面需要带上this
才能够正常访问到。 就像这样this.$on
, this.$emit
...., 我们也知道,this
通常,指向的就是Vue
实例对象。
有了这些认识,我们能不能这样做,我们创建一个Vue实例,然后在A组件中去动态的给这个实例"埋设"一个监听器, 然后再在B组件中去动态触发这些监听器呢?
实际,这就是Vue EventBus 事件总线的核心思路。 它实际上很简洁。下面是一个实现的示例:
// bus.js 实例化一个vue对象,并导出: import Vue from 'vue' export default new Vue();
copy success
再然后再在 A,B 组件中分别去导入:
<script> import Bus from './bus' ....
copy success
A, B 组件分别时这样去定义的:
<!-- A --> <template> <div> <p>Child A</p> <button @click="trigger">emit cusEvent</button> </div> </template> <script> import Bus from './bus' export default { data() { return { name: "jayce", }; }, methods:{ trigger(){ Bus.$emit('cusEvent',this.name) } } }; </script>
copy success
<!-- B --> <template> <p>Child B</p> </template> <script> import Bus from './bus' export default { created(){ Bus.$on('cusEvent',(par)=>{ console.log(par,'--line7'); }) } } </script>
copy success
现在你就能在A组件中通过一个点击事件,将name
这个变量通过自定义事件传参的方式从A传递到B。
简单的描述一下:
我们在B组件中,周期函数created
中,通过Bus.$on
给bus.js "埋设"了一个自定义事件,名为"cusEvent", 且定义了一个形参par
在其回调函数中。
紧接着,我们在A组件中,通过点击事件触发了bus.js 中埋设的事件"cusEvent", 并传入了一个实参name
。
这样,知道点击了A组件中的该按钮,就会触发我们的自定义事件,然后通过回调函数的参数接收到“传参”。
可以简单总结下这个示例:
- "发送参数" 通过
$emit
触发监听器实现,"接受参数"通过$on
监听事件被触发后回调所传入的值得到。 - 你会发现,实际上事件总线,只和两个单独的组件有关,所以理论上,你可以实现任意两个组件之间的传值。而不仅仅是兄弟传值。
【注意事项】:
在该示例中,A组件中,去触发的时候,不能这样去写:
<template> <div> <p>Child A</p> <button @click="Bus.$emit('cusEvent',name)">emit cusEvent</button> </div> </template> <script> import Bus from './bus' export default { data() { return { name: "jayce", }; }, }; </script>
copy success直接把点击事件触发内容定义在Dom上是不可以的,会报错误找不到
Bus
, 而出现这个问题的原因是因为Vue组件的渲染周期导致的。 Vue 组件的dom渲染是异步执行的。 直到mounted
才完成渲染,有可能Bus还没有被import
进来,就渲染解析到了dom上的Bus.$emit....
所以,就找不到,会报错。我们将点击事件触发内容定义在methods中,dom在渲染时,就只会找methods中存不存在对应的方法,在上例中,就是
trigger
这个方法。